/*system header*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/un.h>
#include <sys/socket.h>

/*user header*/
#include <../include/socket_unix.h>

/*global variables*/

/*functions*/

/* Create a stream socket server */
int socket_stream_server(){
    /* Create a stream socket */
    int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(sfd == -1){
        perror("socket");
        return -1;
    }

    /* Remove any existing socket file */
    unlink(SOCKET_ADDR_STREAM);

    /* Set up server address structure */
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_ADDR_STREAM, strlen(SOCKET_ADDR_STREAM)+1);
    
    /* Bind socket to address */
    int err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
    if(err == -1 ){
        perror("bind");
        close(sfd);
        return -1;
    }

    /* Listen for incoming connections */
    err = listen(sfd, SOMAXCONN);
    if(err == -1){
        perror("listen");
        close(sfd);
        return -1;
    }

    /* Client address structure and buffer */
    struct sockaddr_un addr_cli;
    socklen_t len_cli = sizeof(addr_cli);
    char buff[1024];
    ssize_t len;

    /* Main server loop */
    while(1){
        /* Accept incoming connection */
        int cfd = accept(sfd, (struct sockaddr *)&addr_cli, &len_cli);
        if(cfd == -1){
            perror("accept");
            close(sfd);
            return -1;
        }

        /* Receive data from client */
        memset(buff, 0, sizeof(buff));
        len = recv(cfd, buff, sizeof(buff), 0);
        if(len == -1){
            perror("recv");
            close(sfd);
            close(cfd);
            return -1;
        }

        /* Print received message */
        printf("recevied %zd bytes : \n %s \n", len, buff);

        /* Check for exit command */
        if(strcmp(buff,"exit") == 0){
            printf("server closed, see you next time !\n");
            break;
        }
    }

    /* Clean up */
    close(sfd);
    unlink(SOCKET_ADDR_STREAM);
    return 0;
}

/* Create a stream socket client */
int socket_stream_client(int argc, char *argv[]){
    /* Check command line arguments */
    if(argc < 2 || argv[1] == NULL){
        printf("Usage:%s <message send to server>\n", argv[0]);
        return -1;
    }

    /* Create stream socket */
    int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(sfd == -1){
        perror("socket");
        return -1;
    }

    /* Set up server address structure */
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_ADDR_STREAM, strlen(SOCKET_ADDR_STREAM)+1);

    /* Connect to server */
    int err = connect(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
    if(err == -1){
        perror("connect");
        close(sfd);
        return -1;
    }

    /* Send message to server */
    ssize_t len = send(sfd, argv[1], strlen(argv[1]), 0);
    if(len == -1){
        perror("send");
        return -1;
    }

    /* Clean up */
    close(sfd);
    return 0;
}

/* Create a datagram socket server */
int socket_dgram_server(){
    /* Create a datagram socket */
    int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if(sfd == -1){
        perror("socket");
        return -1;
    }

    /* Remove any existing socket file */
    unlink(SOCKET_ADDR_DGRAM);

    /* Set up server address structure */
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_ADDR_DGRAM, strlen(SOCKET_ADDR_DGRAM)+1);

    /* Bind socket to address */
    int err = bind(sfd, (struct sockaddr *)&addr, sizeof(addr.sun_path));
    if(err == -1){
        perror("bind");
        close(sfd);
        return -1;
    }

    /* Buffer for received data */
    char buff[1024];
    /* Client address structure */
    struct sockaddr_un cli_addr;
    socklen_t len = sizeof(cli_addr);

    /* Main server loop */
    while(1){
        memset(buff, 0, sizeof(buff));
        /* Receive data from client */
        ssize_t num_bytes = recvfrom(sfd, buff, sizeof(buff), 0,
                                    (struct sockaddr *)&cli_addr, &len);
        if(num_bytes == -1){
            perror("recvfrom");
            close(sfd);
            return -1;
        }

        /* Print received message */
        printf("received %zd bytes: %s\n", num_bytes, buff);

        /* Check for exit command */
        if(strcmp(buff, "exit") == 0){
            printf("server closed, see you next time!\n");
            break;
        }
    }

    /* Clean up */
    close(sfd);
    unlink(SOCKET_ADDR_DGRAM);
    return 0;
}

/* Create a datagram socket client */
int socket_dgram_client(int argc, char *argv[]){
    /* Check command line arguments */
    if(argc < 2 || argv[1] == NULL){
        printf("Usage:%s <message send to server>\n", argv[0]);
        return -1;
    }

    /* Create datagram socket */
    int sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if(sfd == -1){
        perror("socket");
        return -1;
    }

    /* Set up server address structure */
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SOCKET_ADDR_DGRAM, strlen(SOCKET_ADDR_DGRAM)+1);

    /* Send message to server */
    ssize_t len = sendto(sfd, argv[1], strlen(argv[1]), 0,
                        (struct sockaddr *)&addr, sizeof(addr.sun_path));
    if(len == -1){
        perror("sendto");
        close(sfd);
        return -1;
    }

    /* Clean up */
    close(sfd);
    return 0;
}
